////////////////////////////////
//commandsys.lua
//This file manipulates the console commands.
//DEFINES
////////////////////////////////

function bbb.registercommand(setup)
	//Set up and register command, with basic type checking. See commands.lua
	if(setup==nil) then //NO table passed
		bbb.error("COMMAND REGISTER","Setup table not passed.")
		return false
	end
	if(type(setup.name)!="string" or type(setup.help)!="string" or type(setup.flags)!="table") then //Invalid stuff passed
		bbb.error("COMMAND REGISTER","Name, help or flags was misset.")
		return false
	end
	if(type(setup.func)!="function") then //There is no function for this command
		bbb.error("COMMAND REGISTER","Command \""..setup.name.."\" has invalid function.")
		return false
	end
	if(type(setup.flags[setup.single])!="table" and setup.single!="" and type(setup.single)=="string") then
		bbb.error("COMMAND REGISTER","Command \""..setup.name.."\" has bad single value. Did you leave the \"-\" in the flag?")
		return false
	end
	//index, flag
	for k,v in pairs(setup.flags) do //Check flag structure
		if(type(v.type)!="number" or type(v.help)!="string") then //Bad flag
			bbb.error("COMMAND REGISTER","Command table for \""..setup.name.."\" has flag element missing for \"-"..k.."\".")
			return false
		end
		if(v=="as") then
			bbb.error("COMMAND REGISTER","Command table for \""..setup.name.."\" uses flag \"as\" which is reserved.")
			return false
		end
		if(v.optional==true) then
			if(v.default==nil) then 
				bbb.error("COMMAND REGISTER","Command table for \""..setup.name.."\" is missing default for \"-"..k.."\".")
				return false
			end
			if(bbb.istype(v.default,v.type)==false) then //The default does not match the type.
				bbb.error("COMMAND REGISTER","Command table for \""..setup.name.."\" has bad type/default for \"-"..k.."\".")
				return false
			end
		end
	end
	//It must check out...
	local name=setup.name //Remove extra name element, will be stored in the key
	setup.name=nil
	
	bbb.commands[name]=setup //And now we have a function
	return true
end


function bbb.command(player, command, args)
	if(args==nil) then
		return
	end
	if(args[1]==nil) then
		return
	end
	//Right off of the bat, if no permissions, no run.
	args=table.concat(args," ")
	local pos=string.find(args," ",1,true) //Find the position of the first space
	local cmd,flags
	local f //This contains a table of flags found in the command, or a string if no flags where found.
	if(pos==nil) then //It had no matches, therefore the user is still typing the command.
		if(bbb.commands[args]==nil) then
			bbb.print("Command \""..args.."\" not found.",bbb.USER,{player})
			return false
		else
			//No flags, but a valid command
			f={}
			cmd=args
		end
	else
		cmd=string.sub(args,1,pos-1)
		flags=string.sub(args,pos) //Include the space after the command, before the arguments. Allows any flags to ALWAYS be preceded by a space.
		
		if(bbb.commands[cmd]==nil) then
			bbb.print("Command \""..cmd.."\" not found.",bbb.USER,{player})
			return false
		end
		
		f=bbb.findflags(cmd,flags)
	end
	if(bbb.userhaspermissions(player,bbb.commands[cmd].permissions or bbb.USER)==false) then
		local needed=bbb.uncompress(bbb.commands[cmd].permissions or bbb.USER)
		local neededstring=""
		if(needed[1]==true) then
			neededstring=neededstring.." User"
		end
		if(needed[2]==true) then
			neededstring=neededstring.." Moderator"
		end
		if(needed[3]==true) then
			neededstring=neededstring.." Administrator"
		end
		if(needed[4]==true) then
			neededstring=neededstring.." Owner"
		end
		bbb.print("Need permissions:"..neededstring,bbb.USER,{player})
		return false
	end
	if(type(f)=="string") then //No flags found. Check for single flag availability.
		if(bbb.commands[cmd].single!="" and bbb.commands[cmd].single!=nil) then //There is a single flag option
			f={{flag=bbb.commands[cmd].single,arg=f}} //Set the flag info up for the single flag.
		else
			f={}
		end
	end
	
	local extra2=""
	local flagoutput=""
	local deftab={}
	for k,v in ipairs(f) do //With all of the flags found...
		if(bbb.istype(v.arg,bbb.commands[cmd].flags[v.flag].type)==false) then //It is not of the correct type!
			extra2=extra2.." Invalid type in \""..v.flag.."\"." //Warn the user of this.
		else
			//If it's good, add it to the good outputs.
			flagoutput=flagoutput.."	-"..v.flag.." - '"..tostring(v.arg).."'\n"
		end
		deftab[v.flag]=true //A table of defined values so it doen't miss any optional
	end
		for k,v in pairs(bbb.commands[cmd].flags) do
		if(deftab[k]==nil) then //It isn't typed in
			if(v.optional!=true) then //It isn't optional, and it isn't defined? ERROR
				extra2=extra2.." \""..k.."\" is not optional. Define it."
			else //It is optional and not defined, insert the default values.
				table.insert(f,{flag=k,arg=bbb.commands[cmd].flags[k].default}) //Add default value to the f table.
				flagoutput=flagoutput.."	-"..k.." - '"..tostring(bbb.commands[cmd].flags[k].default.."'\n") //Add the default to the argument listing.
			end
		end
	end
	if(extra2!="") then //There where errors. Don't continue.
		bbb.print("There where errors in your command:\n"..extra2,bbb.CONSOLE)
		return
	end
	
	//Ok, output the generic output, then output the function specific output.
	bbb.print("Running \""..cmd.."\"",bbb.USER,{player})
	if(flagoutput!="") then //Output flag arguments stuff, with trailing newline cut.
		bbb.print(string.sub(flagoutput,1,-2),bbb.USER,{player})
	end
	local input={}
	for k,v in ipairs(f) do //Transcode the f table into properly formatted input for the function
		input[v.flag]=v.arg
	end
	input["as"]=player //Store the player in the command system.
	bbb.commands[cmd].func(input) //Call the defined function with the argument list.
end

function bbb.autocomplete(c,args)
	args=string.sub(args,2) //Trim the space at the start
	local pos=string.find(args," ",1,true) //Find the position of the first space
	if(pos==nil) then //It had no matches, therefore the user is still typing the command.
		local out=bbb.list(args,bbb.commands,true) //You want to search the keys, so the third argument is true
		if(#out==0) then //If there where no possibilities found
			out={"No commands starting with \""..args.."\" found."} //Tell them so
		end
		bbb.tprint(out,bbb.USER,player.GetAll())
		return out
	end

	local cmd
	cmd=string.sub(args,1,pos-1)
	args=string.sub(args,pos) //Include the space after the command, before the arguments. Allows any flags to ALWAYS be preceded by a space.
	
	//The function will handle if the command does not exist.
	local out=bbb.parsecommand(cmd,args)
	bbb.tprint(out,bbb.USER,player.GetAll()) //By the way, screw you garry. You broke autocomplete.
	return out
end

//This will parse the command for the autocomplete
function bbb.parsecommand(cmd,flags)
	if(bbb.commands[cmd]==nil) then //Nonexistant command.
		return "Command \""..cmd.."\" does not exist."
	end
	
	local extra="" //Extra information
	local extra2=""
	local f //This contains a table of flags found in the command, or a string if no flags where found.
	f=bbb.findflags(cmd,flags)
	if(type(f)=="string") then //No flags found. Check for single flag availability.
		if(bbb.commands[cmd].single!="" and bbb.commands[cmd].single!=nil) then //There is a single flag option
			f={{flag=bbb.commands[cmd].single,arg=f}} //Set the flag info up for the single flag.
			extra2=" Defaulting to flag: "..bbb.commands[cmd].single
		else
			f={}
		end
	end
	
	local def="" //Flags that are typed in, in full.
	local deftab={} //Defined table
	local curflag=false
	local curvalue=false
	
	for k,v in ipairs(f) do //With all of the defined flags...
		curflag="'"..tostring(v.arg).."' <"..bbb.typetostring(bbb.commands[cmd].flags[v.flag].type)..">"
		if(bbb.istype(v.arg,bbb.commands[cmd].flags[v.flag].type)==false) then //It is not of the correct type!
			extra2=extra2.." Invalid type in \""..v.flag.."\"." //Warn the user of this.
			curflag="!"..curflag //Another warning.
		end
		def=def.." -"..v.flag.." "..curflag
		deftab[v.flag]=true //A table of defined values so it doesnt repeat
		curflag=v.flag
		curvalue=v.arg
	end
	if(curflag!=false) then //Print out help info.
		extra="-"..curflag.." <"..bbb.typetostring(bbb.commands[cmd].flags[curflag].type).."> - "..bbb.commands[cmd].flags[curflag].help //Add help for the flag
		if(curvalue!=false and bbb.types[bbb.commands[cmd].flags[curflag].type].suggest!=nil) then //It has a suggest function
			extra2=extra2.." Suggestions: "..bbb.types[bbb.commands[cmd].flags[curflag].type].suggest(curvalue)
		end
	end
	local undefo="" //Flags that are missing and optinal
	local undefr="" //Flags that are missing and required
	for k,v in pairs(bbb.commands[cmd].flags) do
		if(deftab[k]==nil) then //It isn't typed in
			curflag="-"..k.." <"..bbb.typetostring(v.type)..">" //Add the argument type
			if(v.optional==true) then //If it is optional parenthesize it
				undefo=undefo.." ("..curflag.." '"..tostring(v.default).."')"
			else
				undefr=undefr.." "..curflag
			end
		end
	end
	deftab={ cmd..def..undefr..undefo } //Empty that for a temp variable
	//Add extra information if applicable
	if(extra!="") then
		table.insert(deftab,extra)
	end
	if(extra2!="") then
		table.insert(deftab,extra2)
	end
	
	return deftab
end


//This will hunt in the string and find and grab the first flag it finds, and return a table with the information about the flag. It starts the search at start.
function bbb.findflag(cmd,flags,start)
	//bbb.print("Searching a flag position.",bbb.DEBUG)
	if(start==nil) then
		start=1
	end
	local len=string.len(flags)
	if(len==1) then //It is just one character, impossible to be a flag
		//bbb.print("Just one character.",bbb.DEBUG)
		return false
	end
	local pos=string.find(flags,"-",start,true) //Find the first dash, starting at start
	if(pos!=nil) then //A dash was found
		if(string.sub(flags,pos-1,pos-1)!=" ") then //This checks to see if the character before the - is a space. If not, it cannot be a flag.
			return false
		end
		//bbb.print("Dash found....",bbb.DEBUG)
		local out={}
		local pos2=string.find(flags," ",pos,true) //Find the first space after the dash.
		if(pos2!=nil) then //A space was found
			if(pos2-1==pos) then //It was a dash with a space after it, no flag
				//bbb.print("Dash space.",bbb.DEBUG)
				return false
			end
		else //No space, therefore...
			pos2=len+1 //The end of the flag is the end of the string, though +1 is needed because of weirdness with find
		end
		pos2=pos2-1 //Compensate for the extra space on the end.
		out.flag=string.sub(flags,pos+1,pos2) //Determine the flag
		//bbb.print("Possible flag found: "..out.flag,bbb.DEBUG)
		if(bbb.commands[cmd].flags[out.flag]!=nil) then //It exists. Notice that the ONLY case a flag can be outputted is if it is valid.
			//bbb.print("Flag found, start: "..pos.." finish: "..pos2.." flag: "..out.flag,bbb.DEBUG)
			out.start=pos-1 //The space at the start is ignored.
			out.finish=pos2
			return out
		end
		return false //There was a flag, but it doesn't count.
	end
	return false //No dashes, therefore nothing found
end

//This function searches through the string and returns a list of flags, and their arguments.
function bbb.findflags(cmd,flags)
	//In this logic, flags can be ONLY valid flags or false. Anything else breaks it.
	//Only return tables from this function.
	local f={}
	local flag1,flag2,flaginfo,start
	flag2=false
	start=1
	while(true) do //When there is no flag info left, it returns, thus breaking out of the while ststment.
		if(flag2==false) then //Find the first flag available.
			//bbb.print("Searching for first flag, starting on: "..tostring(start),bbb.DEBUG)
			flag1=bbb.findflag(cmd,flags,start) //Find the first flag.
			if(flag1!=false) then //If a flag was found...
				//bbb.print("Searching for second flag.",bbb.DEBUG)
				flag2=bbb.findflag(cmd,flags,flag1.finish) //Search for the second occuring flag
			else
				return string.sub(flags,2) //No flags in the entire string, so just return the string as is.
			end
		else
			if(flag2.flag=="") then //There is no remaining flags, because "" is the psudo flag reserved for the end of the string.
				return f
			end
			//The NEW first flag is the second flag. Make it so.
			flag1={}
			flag1.start=flag2.start
			flag1.finish=flag2.finish
			flag1.flag=flag2.flag
			flag2=bbb.findflag(cmd,flags,flag1.finish) //Search the remaining flag info for the next flag
		end
		if(flag1==false) then
			return f //For some reason, the first flag isn't available. No more flags.
		end
		if(flag2==false) then //There is no second flag, therefore the end is the end of the string
			flag2={}
			flag2.start=string.len(flags)+1
			flag2.finish=flag2.start
			flag2.flag="" //The second flag never has it's flag name pulled.
		end
		//There should only be valid flag1 and flag2 information at this point.
		//bbb.print("Flag and flag info found!.",bbb.DEBUG)
		flaginfo={} //Fresh flaginfo...
		flaginfo.flag=flag1.flag //Nab the name
		flaginfo.arg=string.sub(flags,flag1.finish+2,flag2.start-1) //Grab the flag info
		local types=bbb.uncompress(bbb.commands[cmd].flags[flaginfo.flag].type) //Grab the types it should be.
		table.insert(f,flaginfo) //Insert the info
		start=flag2.start //This is precautionary
	end
	return f //Just in case...
end

concommand.Remove("bbb")
concommand.Add("bbb",bbb.command,bbb.autocomplete) //And add the single function.